package parser;

import java.util.ArrayList;

import checker.SemanticException;
import checker.Visitor;
import scanner.LexicalException;
import scanner.Scanner;
import scanner.Token;
import util.AST.AST;
import util.AST.Program;
import util.AST.Command.Command;
import util.AST.Command.FunctionBody;
import util.AST.Command.FunctionDeclaration;
import util.AST.Command.VariableDeclaration;
import util.AST.Expression.BinaryExpression;
import util.AST.Expression.BooleanUnaryExpression;
import util.AST.Expression.Expression;
import util.AST.Expression.IdentifierUnaryExpression;
import util.AST.Expression.NumberUnaryExpression;
import util.AST.Expression.UnaryExpression;
import util.AST.RHS.CallStatementRHS;
import util.AST.RHS.ExpressionRHS;
import util.AST.RHS.RHS;
import util.AST.Statement.AssignStatement;
import util.AST.Statement.BreakStatement;
import util.AST.Statement.CallStatement;
import util.AST.Statement.ContinueStatement;
import util.AST.Statement.IfElseStatement;
import util.AST.Statement.PrintlnStatement;
import util.AST.Statement.ReturnStatement;
import util.AST.Statement.Statement;
import util.AST.Statement.WhileStatement;
import util.AST.Terminal.BooleanValue;
import util.AST.Terminal.Identifier;
import util.AST.Terminal.NumberValue;
import util.AST.Terminal.Operator;
//import util.AST.Terminal.Terminal;
import util.AST.Terminal.Type;

/**
 * Parser class
 * 
 * @version 2010-august-29
 * @discipline Projeto de Compiladores
 * @author Gustavo H P Carvalho
 * @email gustavohpcarvalho@ecomp.poli.br
 */
public class Parser {

	// The current token
	private Token currentToken = null;
	// The scanner
	private Scanner scanner = null;

	/**
	 * Parser constructor
	 * 
	 * @throws LexicalException
	 */
	public Parser() {
		// Initializes the scanner object
		this.scanner = new Scanner();
		acceptIt();
	}

	/**
	 * Veririfes if the current token kind is the expected one
	 * 
	 * @param kind
	 * @throws SyntacticException
	 */
	private void accept(int kind) throws SyntacticException {
		// If the current token kind is equal to the expected
		if (this.currentToken.getKind() == kind) {
			// Gets next token
			acceptIt();
		} else {
			throw new SyntacticException("Wrong Token", currentToken);
		}
	}

	/**
	 * Gets next token
	 * 
	 * @throws LexicalException
	 */
	private void acceptIt() {
		try {
			currentToken = scanner.getNextToken();
		} catch (LexicalException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Verifies if the source program is syntactically correct
	 * 
	 * @throws SyntacticException
	 */

	public AST parse() throws SyntacticException {
		AST p = parseProgram();
		return p;
	}

	// Program -> (Type identifier (; | ( (Parameters | empty) ) ) {
	// FunctionBody})* eot
	public Program parseProgram() throws SyntacticException {
		Program p = null;
		ArrayList<Command> cmds = new ArrayList<Command>();
		Type t = null;
		Identifier i = null;
		String s;

		while (currentToken.getKind() == GrammarSymbols.VOID
				|| currentToken.getKind() == GrammarSymbols.INT
				|| currentToken.getKind() == GrammarSymbols.DOUBLE
				|| currentToken.getKind() == GrammarSymbols.BOOLEAN) {

			t = parseType();
			s = currentToken.getSpelling();
			i = new Identifier(s);
			accept(GrammarSymbols.ID);
			// ;
			// VariableDeclaration = Type Identifier
			if (currentToken.getKind() == GrammarSymbols.SEMICOLON) {
				VariableDeclaration v = new VariableDeclaration(t, i);
				acceptIt();
				cmds.add(v);
			}
			// | ( (Parameters | empty) )
			// FunctionDeclaration = Type Identifier VariableDeclaration*
			// FunctionBody
			else {
				// Nodes
				FunctionDeclaration fd = null;
				ArrayList<VariableDeclaration> parameters = new ArrayList<VariableDeclaration>();
				FunctionBody functionBody = null;

				// Setting up
				accept(GrammarSymbols.LPAR);
				// Parameters -> Type
				if (currentToken.getKind() == GrammarSymbols.VOID
						|| currentToken.getKind() == GrammarSymbols.INT
						|| currentToken.getKind() == GrammarSymbols.DOUBLE
						|| currentToken.getKind() == GrammarSymbols.BOOLEAN) {
					parameters = parseParameters();
				}
				accept(GrammarSymbols.RPAR);
				accept(GrammarSymbols.LBRACKET);
				functionBody = parseFunctionBody();
				accept(GrammarSymbols.RBRACKET);
				fd = new FunctionDeclaration(t, i, parameters, functionBody);
				cmds.add(fd);
			}
		}
		accept(GrammarSymbols.EOT);
		p = new Program(cmds);
		return p;
	}

	// Parameters -> Type identifier (empty | , Parameters)
	public ArrayList<VariableDeclaration> parseParameters()
			throws SyntacticException {
		// Nodes
		ArrayList<VariableDeclaration> parameters = new ArrayList<VariableDeclaration>();
		VariableDeclaration vd = null;
		Type variableType;
		Identifier variableName;
		String s = null;

		// Setting up
		variableType = parseType();
		s = currentToken.getSpelling();
		variableName = new Identifier(s);
		vd = new VariableDeclaration(variableType, variableName);
		parameters.add(vd);
		accept(GrammarSymbols.ID);
		// (empty | , Parameters)
		while (currentToken.getKind() == GrammarSymbols.COMMA) {
			acceptIt();
			variableType = parseType();
			s = currentToken.getSpelling();
			variableName = new Identifier(s);
			vd = new VariableDeclaration(variableType, variableName);
			parameters.add(vd);
			accept(GrammarSymbols.ID);
		}

		// Return the main node
		return parameters;
	}

	// FunctionBody -> (Type identifier ;)* Statements
	public FunctionBody parseFunctionBody() throws SyntacticException {
		FunctionBody fb = null;
		ArrayList<VariableDeclaration> vd = new ArrayList<VariableDeclaration>();
		ArrayList<Statement> stms = new ArrayList<Statement>();
		while (currentToken.getKind() == GrammarSymbols.VOID
				|| currentToken.getKind() == GrammarSymbols.INT
				|| currentToken.getKind() == GrammarSymbols.DOUBLE
				|| currentToken.getKind() == GrammarSymbols.BOOLEAN) {
			Type t = parseType();
			String s = currentToken.getSpelling();
			Identifier i = new Identifier(s);
			accept(GrammarSymbols.ID);
			VariableDeclaration v = new VariableDeclaration(t, i);
			vd.add(v);
			accept(GrammarSymbols.SEMICOLON);
		}
		stms = parseStatements();
		fb = new FunctionBody(vd, stms);
		return fb;
	}

	// Type -> void | int | double | boolean
	public Type parseType() {
		Type t = null;
		// void | int | double | boolean
		if (currentToken.getKind() == GrammarSymbols.VOID
				|| currentToken.getKind() == GrammarSymbols.INT
				|| currentToken.getKind() == GrammarSymbols.DOUBLE
				|| currentToken.getKind() == GrammarSymbols.BOOLEAN) {
			String s = currentToken.getSpelling();
			t = new Type(s);
			acceptIt();
		}
		return t;
	}

	/*
	 * Statement -> identifier (( (Arguments | empty) ) ///////////////////////
	 * *************************| = RHS) ; ////////////////////////////////////
	 * *************| if ( Expression ) { Statements } (empty /////////////////
	 * ************************************************| else { Statements }) /
	 * *************| while ( Expression ) { Statements } /////////////////////
	 * *************| return Expression ; /////////////////////////////////////
	 * *************| return ; ////////////////////////////////////////////////
	 * *************| break ; /////////////////////////////////////////////////
	 * *************| continue ; //////////////////////////////////////////////
	 * *************| println ( identifier ) ; ////////////////////////////////
	 */
	public Statement parseStatement() throws SyntacticException {
		// identifier (( (Arguments | empty) ) | = RHS) ;
		// identifier (= RHS) ;
		if (currentToken.getKind() == GrammarSymbols.ID) {
			String s = currentToken.getSpelling();
			Identifier i = new Identifier(s);
			acceptIt(); // identifier
			if (currentToken.getKind() == GrammarSymbols.LPAR) {
				// CallStatement = Identifier Identifier*
				// Initialization the nodes
				CallStatement call = null;
				ArrayList<Identifier> arguments = new ArrayList<Identifier>();

				// Setting up
				acceptIt();
				if (currentToken.getKind() == GrammarSymbols.ID) {
					arguments = parseArguments();
				}
				
				accept(GrammarSymbols.RPAR);
				call = new CallStatement(i, arguments);
				// accept(GrammarSymbols.COMMA);
				accept(GrammarSymbols.SEMICOLON);
				return call;
			}
			// RHS
			else {
				// AssignStatement = Identifier RHS
				// Initialization the node
				AssignStatement ass = null;
				RHS rightHandStatement = null;

				accept(GrammarSymbols.ASSIGN);
				rightHandStatement = parseRhs();
				// accept(GrammarSymbols.COMMA);
				accept(GrammarSymbols.SEMICOLON);
				ass = new AssignStatement(i, rightHandStatement);
				return ass;
			}
		}
		// if ( Expression ) { Statements } (empty | else { Statements })
		else if (currentToken.getKind() == GrammarSymbols.IF) {
			// Initialization of the Nodes
			IfElseStatement ifElse = null;
			Expression condition;
			ArrayList<Statement> ifStatements = null;
			ArrayList<Statement> elseStatements = null;
			// String s = currentToken.getSpelling();
			// Identifier variableName = new Identifier(s);
			// IdentifierUnaryExpression id = new
			// IdentifierUnaryExpression(variableName);

			// Setting up the nodes
			acceptIt(); // Accept the IF token!
			accept(GrammarSymbols.LPAR);
			condition = parseExpression();
			accept(GrammarSymbols.RPAR);
			accept(GrammarSymbols.LBRACKET);
			ifStatements = parseStatements();

			accept(GrammarSymbols.RBRACKET);
			// (empty| else { Statements })
			if (currentToken.getKind() == GrammarSymbols.ELSE) { // else
				acceptIt();
				accept(GrammarSymbols.LBRACKET);
				elseStatements = parseStatements();
				accept(GrammarSymbols.RBRACKET);
			}
			ifElse = new IfElseStatement(condition, ifStatements,
					elseStatements);
			return ifElse;
		}
		// | while ( Expression ) { Statements }
		else if (currentToken.getKind() == GrammarSymbols.WHILE) {
			// Initialization of the nodes
			WhileStatement wStmnt = null;
			Expression condition;
			ArrayList<Statement> statements;

			// Setting up the nodes
			acceptIt();
			accept(GrammarSymbols.LPAR);
			condition = parseExpression();
			accept(GrammarSymbols.RPAR);
			accept(GrammarSymbols.LBRACKET);
			statements = parseStatements();
			accept(GrammarSymbols.RBRACKET);
			wStmnt = new WhileStatement(condition, statements);

			// Returning the WhileStatement Node
			return wStmnt;

		}
		// | return ; | return Expression ;
		else if (currentToken.getKind() == GrammarSymbols.RETURN) {
			// Initialization of the nodes
			ReturnStatement r = null;
			Expression returnExpression = null;

			acceptIt();
			// | return ;
			if (currentToken.getKind() == GrammarSymbols.SEMICOLON) {
				 
				acceptIt();
			}
			// | return Expression ;
			else {
				returnExpression = parseExpression();
				accept(GrammarSymbols.SEMICOLON);
			}
			//
			r = new ReturnStatement(returnExpression);
			return r;
		}
		// | break ;
		else if (currentToken.getKind() == GrammarSymbols.BREAK) {
			BreakStatement b = new BreakStatement();
			acceptIt();
			accept(GrammarSymbols.SEMICOLON);
			return b;
		}
		// | continue ;
		else if (currentToken.getKind() == GrammarSymbols.CONTINUE) {
			ContinueStatement c = new ContinueStatement();
			acceptIt();
			accept(GrammarSymbols.SEMICOLON);
			return c;
		}
		// | println ( identifier ) ;
		else if (currentToken.getKind() == GrammarSymbols.PRINTLN) {
			// Initialization
			PrintlnStatement p = null;
			Identifier variableName;
			String s;

			// Setting up
			acceptIt();
			accept(GrammarSymbols.LPAR);
			s = currentToken.getSpelling();
			variableName = new Identifier(s);
			accept(GrammarSymbols.ID);
			accept(GrammarSymbols.RPAR);
			accept(GrammarSymbols.SEMICOLON);
			p = new PrintlnStatement(variableName);
			// Return Node
			return p;
		}
		return null;
	}

	public ArrayList<Statement> parseStatements() throws SyntacticException {
		ArrayList<Statement> i = new ArrayList<Statement>();
		while (currentToken.getKind() == GrammarSymbols.ID
				|| currentToken.getKind() == GrammarSymbols.IF
				|| currentToken.getKind() == GrammarSymbols.WHILE
				|| currentToken.getKind() == GrammarSymbols.RETURN
				|| currentToken.getKind() == GrammarSymbols.BREAK
				|| currentToken.getKind() == GrammarSymbols.CONTINUE
				|| currentToken.getKind() == GrammarSymbols.PRINTLN) {
			Statement stmnt = parseStatement();
			i.add(stmnt);
		}
		return i;
	}

	// RHS -> ( Expression ) | identifier ( (Arguments | empty) )
	public RHS parseRhs() throws SyntacticException {
		// ( Expression )
		if (currentToken.getKind() == GrammarSymbols.LPAR) {
			// Nodes
			ExpressionRHS expRHS = null;
			Expression expression;

			// Setting up
			acceptIt();
			expression = parseExpression();
			accept(GrammarSymbols.RPAR);

			// return node
			expRHS = new ExpressionRHS(expression);
			return expRHS;
		}
		// identifier ( (Arguments | empty) )
		else {
			// Nodes
			CallStatementRHS callRHS = null;
			Identifier functionName = null;
			ArrayList<Identifier> arguments = new ArrayList<Identifier>();
			CallStatement call = null;

			// Setting up
			String s = currentToken.getSpelling();
			functionName = new Identifier(s);
			accept(GrammarSymbols.ID);
			accept(GrammarSymbols.LPAR);
			if (currentToken.getKind() == GrammarSymbols.ID) {
				arguments = parseArguments();
			}
			accept(GrammarSymbols.RPAR);
			call = new CallStatement(functionName, arguments);
			callRHS = new CallStatementRHS(call);

			// Return Node
			return callRHS;
		}
	}

	// Arguments -> identifier (empty | , Arguments)
	public ArrayList<Identifier> parseArguments() throws SyntacticException {
		// identifier
		ArrayList<Identifier> parameters = new ArrayList<Identifier>();
		Identifier i = new Identifier(currentToken.getSpelling());
		parameters.add(i);
		accept(GrammarSymbols.ID);
		// (empty | , Arguments)
		while (currentToken.getKind() == GrammarSymbols.COMMA) {
			acceptIt();
			parameters.add(new Identifier(currentToken.getSpelling()));
			accept(GrammarSymbols.ID);
			// parseArguments();
		}
		return parameters;
	}

	public Expression parseExpression() throws SyntacticException {
		// Expression -> ( Expression ( + | - | * | / | == | != | > | >= | < |
		// <= ) Expression ) | Value
		if (currentToken.getKind() == GrammarSymbols.LPAR) {// '('
			acceptIt();
			Expression lExp = (Expression) parseExpression();// Expression
			Operator o = null;
			// Operator ( + | - | * | / | == | != | > | >= | < | <= )
			if (currentToken.getKind() == GrammarSymbols.PLUS
					|| currentToken.getKind() == GrammarSymbols.MINUS
					|| currentToken.getKind() == GrammarSymbols.MULT
					|| currentToken.getKind() == GrammarSymbols.DIV
					|| currentToken.getKind() == GrammarSymbols.EQUAL
					|| currentToken.getKind() == GrammarSymbols.NOTEQUAL
					|| currentToken.getKind() == GrammarSymbols.GREATERTHAN
					|| currentToken.getKind() == GrammarSymbols.GREATEREQUALTHAN
					|| currentToken.getKind() == GrammarSymbols.LESSERTHAN
					|| currentToken.getKind() == GrammarSymbols.LESSEREQUALTHAN) {
				String s = currentToken.getSpelling();
				o = new Operator(s);
				acceptIt();

			}
			// Expression
			Expression rExp = parseExpression();
			// ')'
			accept(GrammarSymbols.RPAR);
			return new BinaryExpression(lExp, o, rExp);
		} else {
			// VALUE
			UnaryExpression u = parseValue();
			return u;
		}
	}

	public UnaryExpression parseValue() {
		String s = currentToken.getSpelling();
		switch (currentToken.getKind()) {
		case GrammarSymbols.ID:
			Identifier id = new Identifier(s);
			IdentifierUnaryExpression i = new IdentifierUnaryExpression(id);
			acceptIt();
			return i;
		case GrammarSymbols.NUMBER:
			NumberValue nv = new NumberValue(s);
			NumberUnaryExpression n = new NumberUnaryExpression(nv);
			acceptIt();
			return n;
		case GrammarSymbols.FALSE:
			BooleanValue bf = new BooleanValue(s);
			BooleanUnaryExpression bfuFalse = new BooleanUnaryExpression(bf);
			acceptIt();
			return bfuFalse;
		case GrammarSymbols.TRUE:
			BooleanValue bt = new BooleanValue(s);
			BooleanUnaryExpression bfuTrue = new BooleanUnaryExpression(bt);
			acceptIt();
			return bfuTrue;
		default:
			break;
		}
		return null;
	}
}
